home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Resources / Developers / XAMPP 1.5.4 / Windows installer / xampp-win32-1.5.4-installer.exe / xampp / php / pear / adodb / adodb-xmlschema.inc.php < prev    next >
Encoding:
PHP Script  |  2006-01-24  |  54.0 KB  |  2,221 lines

  1. <?php
  2. // Copyright (c) 2004 ars Cognita Inc., all rights reserved
  3. /* ******************************************************************************
  4.     Released under both BSD license and Lesser GPL library license. 
  5.      Whenever there is any discrepancy between the two licenses, 
  6.      the BSD license will take precedence. 
  7. *******************************************************************************/
  8. /**
  9.  * xmlschema is a class that allows the user to quickly and easily
  10.  * build a database on any ADOdb-supported platform using a simple
  11.  * XML schema.
  12.  *
  13.  * Last Editor: $Author: jlim $
  14.  * @author Richard Tango-Lowy & Dan Cech
  15.  * @version $Revision: 1.12 $
  16.  *
  17.  * @package axmls
  18.  * @tutorial getting_started.pkg
  19.  */
  20.  
  21. function _file_get_contents($file) 
  22. {
  23.      if (function_exists('file_get_contents')) return file_get_contents($file);
  24.     
  25.     $f = fopen($file,'r');
  26.     if (!$f) return '';
  27.     $t = '';
  28.     
  29.     while ($s = fread($f,100000)) $t .= $s;
  30.     fclose($f);
  31.     return $t;
  32. }
  33.  
  34.  
  35. /**
  36. * Debug on or off
  37. */
  38. if( !defined( 'XMLS_DEBUG' ) ) {
  39.     define( 'XMLS_DEBUG', FALSE );
  40. }
  41.  
  42. /**
  43. * Default prefix key
  44. */
  45. if( !defined( 'XMLS_PREFIX' ) ) {
  46.     define( 'XMLS_PREFIX', '%%P' );
  47. }
  48.  
  49. /**
  50. * Maximum length allowed for object prefix
  51. */
  52. if( !defined( 'XMLS_PREFIX_MAXLEN' ) ) {
  53.     define( 'XMLS_PREFIX_MAXLEN', 10 );
  54. }
  55.  
  56. /**
  57. * Execute SQL inline as it is generated
  58. */
  59. if( !defined( 'XMLS_EXECUTE_INLINE' ) ) {
  60.     define( 'XMLS_EXECUTE_INLINE', FALSE );
  61. }
  62.  
  63. /**
  64. * Continue SQL Execution if an error occurs?
  65. */
  66. if( !defined( 'XMLS_CONTINUE_ON_ERROR' ) ) {
  67.     define( 'XMLS_CONTINUE_ON_ERROR', FALSE );
  68. }
  69.  
  70. /**
  71. * Current Schema Version
  72. */
  73. if( !defined( 'XMLS_SCHEMA_VERSION' ) ) {
  74.     define( 'XMLS_SCHEMA_VERSION', '0.2' );
  75. }
  76.  
  77. /**
  78. * Default Schema Version.  Used for Schemas without an explicit version set.
  79. */
  80. if( !defined( 'XMLS_DEFAULT_SCHEMA_VERSION' ) ) {
  81.     define( 'XMLS_DEFAULT_SCHEMA_VERSION', '0.1' );
  82. }
  83.  
  84. /**
  85. * Default Schema Version.  Used for Schemas without an explicit version set.
  86. */
  87. if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) {
  88.     define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' );
  89. }
  90.  
  91. /**
  92. * Include the main ADODB library
  93. */
  94. if( !defined( '_ADODB_LAYER' ) ) {
  95.     require( 'adodb.inc.php' );
  96.     require( 'adodb-datadict.inc.php' );
  97. }
  98.  
  99. /**
  100. * Abstract DB Object. This class provides basic methods for database objects, such
  101. * as tables and indexes.
  102. *
  103. * @package axmls
  104. * @access private
  105. */
  106. class dbObject {
  107.     
  108.     /**
  109.     * var object Parent
  110.     */
  111.     var $parent;
  112.     
  113.     /**
  114.     * var string current element
  115.     */
  116.     var $currentElement;
  117.     
  118.     /**
  119.     * NOP
  120.     */
  121.     function dbObject( &$parent, $attributes = NULL ) {
  122.         $this->parent =& $parent;
  123.     }
  124.     
  125.     /**
  126.     * XML Callback to process start elements
  127.     *
  128.     * @access private
  129.     */
  130.     function _tag_open( &$parser, $tag, $attributes ) {
  131.         
  132.     }
  133.     
  134.     /**
  135.     * XML Callback to process CDATA elements
  136.     *
  137.     * @access private
  138.     */
  139.     function _tag_cdata( &$parser, $cdata ) {
  140.         
  141.     }
  142.     
  143.     /**
  144.     * XML Callback to process end elements
  145.     *
  146.     * @access private
  147.     */
  148.     function _tag_close( &$parser, $tag ) {
  149.         
  150.     }
  151.     
  152.     function create() {
  153.         return array();
  154.     }
  155.     
  156.     /**
  157.     * Destroys the object
  158.     */
  159.     function destroy() {
  160.         unset( $this );
  161.     }
  162.     
  163.     /**
  164.     * Checks whether the specified RDBMS is supported by the current
  165.     * database object or its ranking ancestor.
  166.     *
  167.     * @param string $platform RDBMS platform name (from ADODB platform list).
  168.     * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
  169.     */
  170.     function supportedPlatform( $platform = NULL ) {
  171.         return is_object( $this->parent ) ? $this->parent->supportedPlatform( $platform ) : TRUE;
  172.     }
  173.     
  174.     /**
  175.     * Returns the prefix set by the ranking ancestor of the database object.
  176.     *
  177.     * @param string $name Prefix string.
  178.     * @return string Prefix.
  179.     */
  180.     function prefix( $name = '' ) {
  181.         return is_object( $this->parent ) ? $this->parent->prefix( $name ) : $name;
  182.     }
  183.     
  184.     /**
  185.     * Extracts a field ID from the specified field.
  186.     *
  187.     * @param string $field Field.
  188.     * @return string Field ID.
  189.     */
  190.     function FieldID( $field ) {
  191.         return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) );
  192.     }
  193. }
  194.  
  195. /**
  196. * Creates a table object in ADOdb's datadict format
  197. *
  198. * This class stores information about a database table. As charactaristics
  199. * of the table are loaded from the external source, methods and properties
  200. * of this class are used to build up the table description in ADOdb's
  201. * datadict format.
  202. *
  203. * @package axmls
  204. * @access private
  205. */
  206. class dbTable extends dbObject {
  207.     
  208.     /**
  209.     * @var string Table name
  210.     */
  211.     var $name;
  212.     
  213.     /**
  214.     * @var array Field specifier: Meta-information about each field
  215.     */
  216.     var $fields = array();
  217.     
  218.     /**
  219.     * @var array List of table indexes.
  220.     */
  221.     var $indexes = array();
  222.     
  223.     /**
  224.     * @var array Table options: Table-level options
  225.     */
  226.     var $opts = array();
  227.     
  228.     /**
  229.     * @var string Field index: Keeps track of which field is currently being processed
  230.     */
  231.     var $current_field;
  232.     
  233.     /**
  234.     * @var boolean Mark table for destruction
  235.     * @access private
  236.     */
  237.     var $drop_table;
  238.     
  239.     /**
  240.     * @var boolean Mark field for destruction (not yet implemented)
  241.     * @access private
  242.     */
  243.     var $drop_field = array();
  244.     
  245.     /**
  246.     * Iniitializes a new table object.
  247.     *
  248.     * @param string $prefix DB Object prefix
  249.     * @param array $attributes Array of table attributes.
  250.     */
  251.     function dbTable( &$parent, $attributes = NULL ) {
  252.         $this->parent =& $parent;
  253.         $this->name = $this->prefix($attributes['NAME']);
  254.     }
  255.     
  256.     /**
  257.     * XML Callback to process start elements. Elements currently 
  258.     * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT. 
  259.     *
  260.     * @access private
  261.     */
  262.     function _tag_open( &$parser, $tag, $attributes ) {
  263.         $this->currentElement = strtoupper( $tag );
  264.         
  265.         switch( $this->currentElement ) {
  266.             case 'INDEX':
  267.                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  268.                     xml_set_object( $parser, $this->addIndex( $attributes ) );
  269.                 }
  270.                 break;
  271.             case 'DATA':
  272.                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  273.                     xml_set_object( $parser, $this->addData( $attributes ) );
  274.                 }
  275.                 break;
  276.             case 'DROP':
  277.                 $this->drop();
  278.                 break;
  279.             case 'FIELD':
  280.                 // Add a field
  281.                 $fieldName = $attributes['NAME'];
  282.                 $fieldType = $attributes['TYPE'];
  283.                 $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL;
  284.                 $fieldOpts = isset( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL;
  285.                 
  286.                 $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts );
  287.                 break;
  288.             case 'KEY':
  289.             case 'NOTNULL':
  290.             case 'AUTOINCREMENT':
  291.                 // Add a field option
  292.                 $this->addFieldOpt( $this->current_field, $this->currentElement );
  293.                 break;
  294.             case 'DEFAULT':
  295.                 // Add a field option to the table object
  296.                 
  297.                 // Work around ADOdb datadict issue that misinterprets empty strings.
  298.                 if( $attributes['VALUE'] == '' ) {
  299.                     $attributes['VALUE'] = " '' ";
  300.                 }
  301.                 
  302.                 $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] );
  303.                 break;
  304.             case 'DEFDATE':
  305.             case 'DEFTIMESTAMP':
  306.                 // Add a field option to the table object
  307.                 $this->addFieldOpt( $this->current_field, $this->currentElement );
  308.                 break;
  309.             default:
  310.                 // print_r( array( $tag, $attributes ) );
  311.         }
  312.     }
  313.     
  314.     /**
  315.     * XML Callback to process CDATA elements
  316.     *
  317.     * @access private
  318.     */
  319.     function _tag_cdata( &$parser, $cdata ) {
  320.         switch( $this->currentElement ) {
  321.             // Table constraint
  322.             case 'CONSTRAINT':
  323.                 if( isset( $this->current_field ) ) {
  324.                     $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata );
  325.                 } else {
  326.                     $this->addTableOpt( $cdata );
  327.                 }
  328.                 break;
  329.             // Table option
  330.             case 'OPT':
  331.                 $this->addTableOpt( $cdata );
  332.                 break;
  333.             default:
  334.                 
  335.         }
  336.     }
  337.     
  338.     /**
  339.     * XML Callback to process end elements
  340.     *
  341.     * @access private
  342.     */
  343.     function _tag_close( &$parser, $tag ) {
  344.         $this->currentElement = '';
  345.         
  346.         switch( strtoupper( $tag ) ) {
  347.             case 'TABLE':
  348.                 $this->parent->addSQL( $this->create( $this->parent ) );
  349.                 xml_set_object( $parser, $this->parent );
  350.                 $this->destroy();
  351.                 break;
  352.             case 'FIELD':
  353.                 unset($this->current_field);
  354.                 break;
  355.  
  356.         }
  357.     }
  358.     
  359.     /**
  360.     * Adds an index to a table object
  361.     *
  362.     * @param array $attributes Index attributes
  363.     * @return object dbIndex object
  364.     */
  365.     function &addIndex( $attributes ) {
  366.         $name = strtoupper( $attributes['NAME'] );
  367.         $this->indexes[$name] =& new dbIndex( $this, $attributes );
  368.         return $this->indexes[$name];
  369.     }
  370.     
  371.     /**
  372.     * Adds data to a table object
  373.     *
  374.     * @param array $attributes Data attributes
  375.     * @return object dbData object
  376.     */
  377.     function &addData( $attributes ) {
  378.         if( !isset( $this->data ) ) {
  379.             $this->data =& new dbData( $this, $attributes );
  380.         }
  381.         return $this->data;
  382.     }
  383.     
  384.     /**
  385.     * Adds a field to a table object
  386.     *
  387.     * $name is the name of the table to which the field should be added. 
  388.     * $type is an ADODB datadict field type. The following field types
  389.     * are supported as of ADODB 3.40:
  390.     *     - C:  varchar
  391.     *    - X:  CLOB (character large object) or largest varchar size
  392.     *       if CLOB is not supported
  393.     *    - C2: Multibyte varchar
  394.     *    - X2: Multibyte CLOB
  395.     *    - B:  BLOB (binary large object)
  396.     *    - D:  Date (some databases do not support this, and we return a datetime type)
  397.     *    - T:  Datetime or Timestamp
  398.     *    - L:  Integer field suitable for storing booleans (0 or 1)
  399.     *    - I:  Integer (mapped to I4)
  400.     *    - I1: 1-byte integer
  401.     *    - I2: 2-byte integer
  402.     *    - I4: 4-byte integer
  403.     *    - I8: 8-byte integer
  404.     *    - F:  Floating point number
  405.     *    - N:  Numeric or decimal number
  406.     *
  407.     * @param string $name Name of the table to which the field will be added.
  408.     * @param string $type    ADODB datadict field type.
  409.     * @param string $size    Field size
  410.     * @param array $opts    Field options array
  411.     * @return array Field specifier array
  412.     */
  413.     function addField( $name, $type, $size = NULL, $opts = NULL ) {
  414.         $field_id = $this->FieldID( $name );
  415.         
  416.         // Set the field index so we know where we are
  417.         $this->current_field = $field_id;
  418.         
  419.         // Set the field name (required)
  420.         $this->fields[$field_id]['NAME'] = $name;
  421.         
  422.         // Set the field type (required)
  423.         $this->fields[$field_id]['TYPE'] = $type;
  424.         
  425.         // Set the field size (optional)
  426.         if( isset( $size ) ) {
  427.             $this->fields[$field_id]['SIZE'] = $size;
  428.         }
  429.         
  430.         // Set the field options
  431.         if( isset( $opts ) ) {
  432.             $this->fields[$field_id]['OPTS'][] = $opts;
  433.         }
  434.     }
  435.     
  436.     /**
  437.     * Adds a field option to the current field specifier
  438.     *
  439.     * This method adds a field option allowed by the ADOdb datadict 
  440.     * and appends it to the given field.
  441.     *
  442.     * @param string $field    Field name
  443.     * @param string $opt ADOdb field option
  444.     * @param mixed $value Field option value
  445.     * @return array Field specifier array
  446.     */
  447.     function addFieldOpt( $field, $opt, $value = NULL ) {
  448.         if( !isset( $value ) ) {
  449.             $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt;
  450.         // Add the option and value
  451.         } else {
  452.             $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value );
  453.         }
  454.     }
  455.     
  456.     /**
  457.     * Adds an option to the table
  458.     *
  459.     * This method takes a comma-separated list of table-level options
  460.     * and appends them to the table object.
  461.     *
  462.     * @param string $opt Table option
  463.     * @return array Options
  464.     */
  465.     function addTableOpt( $opt ) {
  466.         $this->opts[] = $opt;
  467.         
  468.         return $this->opts;
  469.     }
  470.     
  471.     /**
  472.     * Generates the SQL that will create the table in the database
  473.     *
  474.     * @param object $xmls adoSchema object
  475.     * @return array Array containing table creation SQL
  476.     */
  477.     function create( &$xmls ) {
  478.         $sql = array();
  479.         
  480.         // drop any existing indexes
  481.         if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) {
  482.             foreach( $legacy_indexes as $index => $index_details ) {
  483.                 $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name );
  484.             }
  485.         }
  486.         
  487.         // remove fields to be dropped from table object
  488.         foreach( $this->drop_field as $field ) {
  489.             unset( $this->fields[$field] );
  490.         }
  491.         
  492.         // if table exists
  493.         if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) {
  494.             // drop table
  495.             if( $this->drop_table ) {
  496.                 $sql[] = $xmls->dict->DropTableSQL( $this->name );
  497.                 
  498.                 return $sql;
  499.             }
  500.             
  501.             // drop any existing fields not in schema
  502.             foreach( $legacy_fields as $field_id => $field ) {
  503.                 if( !isset( $this->fields[$field_id] ) ) {
  504.                     $sql[] = $xmls->dict->DropColumnSQL( $this->name, '`'.$field->name.'`' );
  505.                 }
  506.             }
  507.         // if table doesn't exist
  508.         } else {
  509.             if( $this->drop_table ) {
  510.                 return $sql;
  511.             }
  512.             
  513.             $legacy_fields = array();
  514.         }
  515.         
  516.         // Loop through the field specifier array, building the associative array for the field options
  517.         $fldarray = array();
  518.         
  519.         foreach( $this->fields as $field_id => $finfo ) {
  520.             // Set an empty size if it isn't supplied
  521.             if( !isset( $finfo['SIZE'] ) ) {
  522.                 $finfo['SIZE'] = '';
  523.             }
  524.             
  525.             // Initialize the field array with the type and size
  526.             $fldarray[$field_id] = array(
  527.                 'NAME' => $finfo['NAME'],
  528.                 'TYPE' => $finfo['TYPE'],
  529.                 'SIZE' => $finfo['SIZE']
  530.             );
  531.             
  532.             // Loop through the options array and add the field options. 
  533.             if( isset( $finfo['OPTS'] ) ) {
  534.                 foreach( $finfo['OPTS'] as $opt ) {
  535.                     // Option has an argument.
  536.                     if( is_array( $opt ) ) {
  537.                         $key = key( $opt );
  538.                         $value = $opt[key( $opt )];
  539.                         @$fldarray[$field_id][$key] .= $value;
  540.                     // Option doesn't have arguments
  541.                     } else {
  542.                         $fldarray[$field_id][$opt] = $opt;
  543.                     }
  544.                 }
  545.             }
  546.         }
  547.         
  548.         if( empty( $legacy_fields ) ) {
  549.             // Create the new table
  550.             $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
  551.             logMsg( end( $sql ), 'Generated CreateTableSQL' );
  552.         } else {
  553.             // Upgrade an existing table
  554.             logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" );
  555.             switch( $xmls->upgrade ) {
  556.                 // Use ChangeTableSQL
  557.                 case 'ALTER':
  558.                     logMsg( 'Generated ChangeTableSQL (ALTERing table)' );
  559.                     $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts );
  560.                     break;
  561.                 case 'REPLACE':
  562.                     logMsg( 'Doing upgrade REPLACE (testing)' );
  563.                     $sql[] = $xmls->dict->DropTableSQL( $this->name );
  564.                     $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
  565.                     break;
  566.                 // ignore table
  567.                 default:
  568.                     return array();
  569.             }
  570.         }
  571.         
  572.         foreach( $this->indexes as $index ) {
  573.             $sql[] = $index->create( $xmls );
  574.         }
  575.         
  576.         if( isset( $this->data ) ) {
  577.             $sql[] = $this->data->create( $xmls );
  578.         }
  579.         
  580.         return $sql;
  581.     }
  582.     
  583.     /**
  584.     * Marks a field or table for destruction
  585.     */
  586.     function drop() {
  587.         if( isset( $this->current_field ) ) {
  588.             // Drop the current field
  589.             logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" );
  590.             // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field );
  591.             $this->drop_field[$this->current_field] = $this->current_field;
  592.         } else {
  593.             // Drop the current table
  594.             logMsg( "Dropping table '{$this->name}'" );
  595.             // $this->drop_table = $xmls->dict->DropTableSQL( $this->name );
  596.             $this->drop_table = TRUE;
  597.         }
  598.     }
  599. }
  600.  
  601. /**
  602. * Creates an index object in ADOdb's datadict format
  603. *
  604. * This class stores information about a database index. As charactaristics
  605. * of the index are loaded from the external source, methods and properties
  606. * of this class are used to build up the index description in ADOdb's
  607. * datadict format.
  608. *
  609. * @package axmls
  610. * @access private
  611. */
  612. class dbIndex extends dbObject {
  613.     
  614.     /**
  615.     * @var string    Index name
  616.     */
  617.     var $name;
  618.     
  619.     /**
  620.     * @var array    Index options: Index-level options
  621.     */
  622.     var $opts = array();
  623.     
  624.     /**
  625.     * @var array    Indexed fields: Table columns included in this index
  626.     */
  627.     var $columns = array();
  628.     
  629.     /**
  630.     * @var boolean Mark index for destruction
  631.     * @access private
  632.     */
  633.     var $drop = FALSE;
  634.     
  635.     /**
  636.     * Initializes the new dbIndex object.
  637.     *
  638.     * @param object $parent Parent object
  639.     * @param array $attributes Attributes
  640.     *
  641.     * @internal
  642.     */
  643.     function dbIndex( &$parent, $attributes = NULL ) {
  644.         $this->parent =& $parent;
  645.         
  646.         $this->name = $this->prefix ($attributes['NAME']);
  647.     }
  648.     
  649.     /**
  650.     * XML Callback to process start elements
  651.     *
  652.     * Processes XML opening tags. 
  653.     * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH. 
  654.     *
  655.     * @access private
  656.     */
  657.     function _tag_open( &$parser, $tag, $attributes ) {
  658.         $this->currentElement = strtoupper( $tag );
  659.         
  660.         switch( $this->currentElement ) {
  661.             case 'DROP':
  662.                 $this->drop();
  663.                 break;
  664.             case 'CLUSTERED':
  665.             case 'BITMAP':
  666.             case 'UNIQUE':
  667.             case 'FULLTEXT':
  668.             case 'HASH':
  669.                 // Add index Option
  670.                 $this->addIndexOpt( $this->currentElement );
  671.                 break;
  672.             default:
  673.                 // print_r( array( $tag, $attributes ) );
  674.         }
  675.     }
  676.     
  677.     /**
  678.     * XML Callback to process CDATA elements
  679.     *
  680.     * Processes XML cdata.
  681.     *
  682.     * @access private
  683.     */
  684.     function _tag_cdata( &$parser, $cdata ) {
  685.         switch( $this->currentElement ) {
  686.             // Index field name
  687.             case 'COL':
  688.                 $this->addField( $cdata );
  689.                 break;
  690.             default:
  691.                 
  692.         }
  693.     }
  694.     
  695.     /**
  696.     * XML Callback to process end elements
  697.     *
  698.     * @access private
  699.     */
  700.     function _tag_close( &$parser, $tag ) {
  701.         $this->currentElement = '';
  702.         
  703.         switch( strtoupper( $tag ) ) {
  704.             case 'INDEX':
  705.                 xml_set_object( $parser, $this->parent );
  706.                 break;
  707.         }
  708.     }
  709.     
  710.     /**
  711.     * Adds a field to the index
  712.     *
  713.     * @param string $name Field name
  714.     * @return string Field list
  715.     */
  716.     function addField( $name ) {
  717.         $this->columns[$this->FieldID( $name )] = $name;
  718.         
  719.         // Return the field list
  720.         return $this->columns;
  721.     }
  722.     
  723.     /**
  724.     * Adds options to the index
  725.     *
  726.     * @param string $opt Comma-separated list of index options.
  727.     * @return string Option list
  728.     */
  729.     function addIndexOpt( $opt ) {
  730.         $this->opts[] = $opt;
  731.         
  732.         // Return the options list
  733.         return $this->opts;
  734.     }
  735.     
  736.     /**
  737.     * Generates the SQL that will create the index in the database
  738.     *
  739.     * @param object $xmls adoSchema object
  740.     * @return array Array containing index creation SQL
  741.     */
  742.     function create( &$xmls ) {
  743.         if( $this->drop ) {
  744.             return NULL;
  745.         }
  746.         
  747.         // eliminate any columns that aren't in the table
  748.         foreach( $this->columns as $id => $col ) {
  749.             if( !isset( $this->parent->fields[$id] ) ) {
  750.                 unset( $this->columns[$id] );
  751.             }
  752.         }
  753.         
  754.         return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts );
  755.     }
  756.     
  757.     /**
  758.     * Marks an index for destruction
  759.     */
  760.     function drop() {
  761.         $this->drop = TRUE;
  762.     }
  763. }
  764.  
  765. /**
  766. * Creates a data object in ADOdb's datadict format
  767. *
  768. * This class stores information about table data.
  769. *
  770. * @package axmls
  771. * @access private
  772. */
  773. class dbData extends dbObject {
  774.     
  775.     var $data = array();
  776.     
  777.     var $row;
  778.     
  779.     /**
  780.     * Initializes the new dbIndex object.
  781.     *
  782.     * @param object $parent Parent object
  783.     * @param array $attributes Attributes
  784.     *
  785.     * @internal
  786.     */
  787.     function dbData( &$parent, $attributes = NULL ) {
  788.         $this->parent =& $parent;
  789.     }
  790.     
  791.     /**
  792.     * XML Callback to process start elements
  793.     *
  794.     * Processes XML opening tags. 
  795.     * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH. 
  796.     *
  797.     * @access private
  798.     */
  799.     function _tag_open( &$parser, $tag, $attributes ) {
  800.         $this->currentElement = strtoupper( $tag );
  801.         
  802.         switch( $this->currentElement ) {
  803.             case 'ROW':
  804.                 $this->row = count( $this->data );
  805.                 $this->data[$this->row] = array();
  806.                 break;
  807.             case 'F':
  808.                 $this->addField($attributes);
  809.             default:
  810.                 // print_r( array( $tag, $attributes ) );
  811.         }
  812.     }
  813.     
  814.     /**
  815.     * XML Callback to process CDATA elements
  816.     *
  817.     * Processes XML cdata.
  818.     *
  819.     * @access private
  820.     */
  821.     function _tag_cdata( &$parser, $cdata ) {
  822.         switch( $this->currentElement ) {
  823.             // Index field name
  824.             case 'F':
  825.                 $this->addData( $cdata );
  826.                 break;
  827.             default:
  828.                 
  829.         }
  830.     }
  831.     
  832.     /**
  833.     * XML Callback to process end elements
  834.     *
  835.     * @access private
  836.     */
  837.     function _tag_close( &$parser, $tag ) {
  838.         $this->currentElement = '';
  839.         
  840.         switch( strtoupper( $tag ) ) {
  841.             case 'DATA':
  842.                 xml_set_object( $parser, $this->parent );
  843.                 break;
  844.         }
  845.     }
  846.     
  847.     /**
  848.     * Adds a field to the index
  849.     *
  850.     * @param string $name Field name
  851.     * @return string Field list
  852.     */
  853.     function addField( $attributes ) {
  854.         if( isset( $attributes['NAME'] ) ) {
  855.             $name = $attributes['NAME'];
  856.         } else {
  857.             $name = count($this->data[$this->row]);
  858.         }
  859.         
  860.         // Set the field index so we know where we are
  861.         $this->current_field = $this->FieldID( $name );
  862.     }
  863.     
  864.     /**
  865.     * Adds options to the index
  866.     *
  867.     * @param string $opt Comma-separated list of index options.
  868.     * @return string Option list
  869.     */
  870.     function addData( $cdata ) {
  871.         if( !isset( $this->data[$this->row] ) ) {
  872.             $this->data[$this->row] = array();
  873.         }
  874.         
  875.         if( !isset( $this->data[$this->row][$this->current_field] ) ) {
  876.             $this->data[$this->row][$this->current_field] = '';
  877.         }
  878.         
  879.         $this->data[$this->row][$this->current_field] .= $cdata;
  880.     }
  881.     
  882.     /**
  883.     * Generates the SQL that will create the index in the database
  884.     *
  885.     * @param object $xmls adoSchema object
  886.     * @return array Array containing index creation SQL
  887.     */
  888.     function create( &$xmls ) {
  889.         $table = $xmls->dict->TableName($this->parent->name);
  890.         $table_field_count = count($this->parent->fields);
  891.         $sql = array();
  892.         
  893.         // eliminate any columns that aren't in the table
  894.         foreach( $this->data as $row ) {
  895.             $table_fields = $this->parent->fields;
  896.             $fields = array();
  897.             
  898.             foreach( $row as $field_id => $field_data ) {
  899.                 if( !array_key_exists( $field_id, $table_fields ) ) {
  900.                     if( is_numeric( $field_id ) ) {
  901.                         $field_id = reset( array_keys( $table_fields ) );
  902.                     } else {
  903.                         continue;
  904.                     }
  905.                 }
  906.                 
  907.                 $name = $table_fields[$field_id]['NAME'];
  908.                 
  909.                 switch( $table_fields[$field_id]['TYPE'] ) {
  910.                     case 'C':
  911.                     case 'C2':
  912.                     case 'X':
  913.                     case 'X2':
  914.                         $fields[$name] = $xmls->db->qstr( $field_data );
  915.                         break;
  916.                     case 'I':
  917.                     case 'I1':
  918.                     case 'I2':
  919.                     case 'I4':
  920.                     case 'I8':
  921.                         $fields[$name] = intval($field_data);
  922.                         break;
  923.                     default:
  924.                         $fields[$name] = $field_data;
  925.                 }
  926.                 
  927.                 unset($table_fields[$field_id]);
  928.             }
  929.             
  930.             // check that at least 1 column is specified
  931.             if( empty( $fields ) ) {
  932.                 continue;
  933.             }
  934.             
  935.             // check that no required columns are missing
  936.             if( count( $fields ) < $table_field_count ) {
  937.                 foreach( $table_fields as $field ) {
  938.                     if (isset( $field['OPTS'] ))
  939.                         if( ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) {
  940.                             continue(2);
  941.                         }
  942.                 }
  943.             }
  944.             
  945.             $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')';
  946.         }
  947.         
  948.         return $sql;
  949.     }
  950. }
  951.  
  952. /**
  953. * Creates the SQL to execute a list of provided SQL queries
  954. *
  955. * @package axmls
  956. * @access private
  957. */
  958. class dbQuerySet extends dbObject {
  959.     
  960.     /**
  961.     * @var array    List of SQL queries
  962.     */
  963.     var $queries = array();
  964.     
  965.     /**
  966.     * @var string    String used to build of a query line by line
  967.     */
  968.     var $query;
  969.     
  970.     /**
  971.     * @var string    Query prefix key
  972.     */
  973.     var $prefixKey = '';
  974.     
  975.     /**
  976.     * @var boolean    Auto prefix enable (TRUE)
  977.     */
  978.     var $prefixMethod = 'AUTO';
  979.     
  980.     /**
  981.     * Initializes the query set.
  982.     *
  983.     * @param object $parent Parent object
  984.     * @param array $attributes Attributes
  985.     */
  986.     function dbQuerySet( &$parent, $attributes = NULL ) {
  987.         $this->parent =& $parent;
  988.             
  989.         // Overrides the manual prefix key
  990.         if( isset( $attributes['KEY'] ) ) {
  991.             $this->prefixKey = $attributes['KEY'];
  992.         }
  993.         
  994.         $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : '';
  995.         
  996.         // Enables or disables automatic prefix prepending
  997.         switch( $prefixMethod ) {
  998.             case 'AUTO':
  999.                 $this->prefixMethod = 'AUTO';
  1000.                 break;
  1001.             case 'MANUAL':
  1002.                 $this->prefixMethod = 'MANUAL';
  1003.                 break;
  1004.             case 'NONE':
  1005.                 $this->prefixMethod = 'NONE';
  1006.                 break;
  1007.         }
  1008.     }
  1009.     
  1010.     /**
  1011.     * XML Callback to process start elements. Elements currently 
  1012.     * processed are: QUERY. 
  1013.     *
  1014.     * @access private
  1015.     */
  1016.     function _tag_open( &$parser, $tag, $attributes ) {
  1017.         $this->currentElement = strtoupper( $tag );
  1018.         
  1019.         switch( $this->currentElement ) {
  1020.             case 'QUERY':
  1021.                 // Create a new query in a SQL queryset.
  1022.                 // Ignore this query set if a platform is specified and it's different than the 
  1023.                 // current connection platform.
  1024.                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  1025.                     $this->newQuery();
  1026.                 } else {
  1027.                     $this->discardQuery();
  1028.                 }
  1029.                 break;
  1030.             default:
  1031.                 // print_r( array( $tag, $attributes ) );
  1032.         }
  1033.     }
  1034.     
  1035.     /**
  1036.     * XML Callback to process CDATA elements
  1037.     */
  1038.     function _tag_cdata( &$parser, $cdata ) {
  1039.         switch( $this->currentElement ) {
  1040.             // Line of queryset SQL data
  1041.             case 'QUERY':
  1042.                 $this->buildQuery( $cdata );
  1043.                 break;
  1044.             default:
  1045.                 
  1046.         }
  1047.     }
  1048.     
  1049.     /**
  1050.     * XML Callback to process end elements
  1051.     *
  1052.     * @access private
  1053.     */
  1054.     function _tag_close( &$parser, $tag ) {
  1055.         $this->currentElement = '';
  1056.         
  1057.         switch( strtoupper( $tag ) ) {
  1058.             case 'QUERY':
  1059.                 // Add the finished query to the open query set.
  1060.                 $this->addQuery();
  1061.                 break;
  1062.             case 'SQL':
  1063.                 $this->parent->addSQL( $this->create( $this->parent ) );
  1064.                 xml_set_object( $parser, $this->parent );
  1065.                 $this->destroy();
  1066.                 break;
  1067.             default:
  1068.                 
  1069.         }
  1070.     }
  1071.     
  1072.     /**
  1073.     * Re-initializes the query.
  1074.     *
  1075.     * @return boolean TRUE
  1076.     */
  1077.     function newQuery() {
  1078.         $this->query = '';
  1079.         
  1080.         return TRUE;
  1081.     }
  1082.     
  1083.     /**
  1084.     * Discards the existing query.
  1085.     *
  1086.     * @return boolean TRUE
  1087.     */
  1088.     function discardQuery() {
  1089.         unset( $this->query );
  1090.         
  1091.         return TRUE;
  1092.     }
  1093.     
  1094.     /** 
  1095.     * Appends a line to a query that is being built line by line
  1096.     *
  1097.     * @param string $data Line of SQL data or NULL to initialize a new query
  1098.     * @return string SQL query string.
  1099.     */
  1100.     function buildQuery( $sql = NULL ) {
  1101.         if( !isset( $this->query ) OR empty( $sql ) ) {
  1102.             return FALSE;
  1103.         }
  1104.         
  1105.         $this->query .= $sql;
  1106.         
  1107.         return $this->query;
  1108.     }
  1109.     
  1110.     /**
  1111.     * Adds a completed query to the query list
  1112.     *
  1113.     * @return string    SQL of added query
  1114.     */
  1115.     function addQuery() {
  1116.         if( !isset( $this->query ) ) {
  1117.             return FALSE;
  1118.         }
  1119.         
  1120.         $this->queries[] = $return = trim($this->query);
  1121.         
  1122.         unset( $this->query );
  1123.         
  1124.         return $return;
  1125.     }
  1126.     
  1127.     /**
  1128.     * Creates and returns the current query set
  1129.     *
  1130.     * @param object $xmls adoSchema object
  1131.     * @return array Query set
  1132.     */
  1133.     function create( &$xmls ) {
  1134.         foreach( $this->queries as $id => $query ) {
  1135.             switch( $this->prefixMethod ) {
  1136.                 case 'AUTO':
  1137.                     // Enable auto prefix replacement
  1138.                     
  1139.                     // Process object prefix.
  1140.                     // Evaluate SQL statements to prepend prefix to objects
  1141.                     $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
  1142.                     $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
  1143.                     $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
  1144.                     
  1145.                     // SELECT statements aren't working yet
  1146.                     #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data );
  1147.                     
  1148.                 case 'MANUAL':
  1149.                     // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX.
  1150.                     // If prefixKey is not set, we use the default constant XMLS_PREFIX
  1151.                     if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) {
  1152.                         // Enable prefix override
  1153.                         $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query );
  1154.                     } else {
  1155.                         // Use default replacement
  1156.                         $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query );
  1157.                     }
  1158.             }
  1159.             
  1160.             $this->queries[$id] = trim( $query );
  1161.         }
  1162.         
  1163.         // Return the query set array
  1164.         return $this->queries;
  1165.     }
  1166.     
  1167.     /**
  1168.     * Rebuilds the query with the prefix attached to any objects
  1169.     *
  1170.     * @param string $regex Regex used to add prefix
  1171.     * @param string $query SQL query string
  1172.     * @param string $prefix Prefix to be appended to tables, indices, etc.
  1173.     * @return string Prefixed SQL query string.
  1174.     */
  1175.     function prefixQuery( $regex, $query, $prefix = NULL ) {
  1176.         if( !isset( $prefix ) ) {
  1177.             return $query;
  1178.         }
  1179.         
  1180.         if( preg_match( $regex, $query, $match ) ) {
  1181.             $preamble = $match[1];
  1182.             $postamble = $match[5];
  1183.             $objectList = explode( ',', $match[3] );
  1184.             // $prefix = $prefix . '_';
  1185.             
  1186.             $prefixedList = '';
  1187.             
  1188.             foreach( $objectList as $object ) {
  1189.                 if( $prefixedList !== '' ) {
  1190.                     $prefixedList .= ', ';
  1191.                 }
  1192.                 
  1193.                 $prefixedList .= $prefix . trim( $object );
  1194.             }
  1195.             
  1196.             $query = $preamble . ' ' . $prefixedList . ' ' . $postamble;
  1197.         }
  1198.         
  1199.         return $query;
  1200.     }
  1201. }
  1202.  
  1203. /**
  1204. * Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
  1205. * This class is used to load and parse the XML file, to create an array of SQL statements
  1206. * that can be used to build a database, and to build the database using the SQL array.
  1207. *
  1208. * @tutorial getting_started.pkg
  1209. *
  1210. * @author Richard Tango-Lowy & Dan Cech
  1211. * @version $Revision: 1.12 $
  1212. *
  1213. * @package axmls
  1214. */
  1215. class adoSchema {
  1216.     
  1217.     /**
  1218.     * @var array    Array containing SQL queries to generate all objects
  1219.     * @access private
  1220.     */
  1221.     var $sqlArray;
  1222.     
  1223.     /**
  1224.     * @var object    ADOdb connection object
  1225.     * @access private
  1226.     */
  1227.     var $db;
  1228.     
  1229.     /**
  1230.     * @var object    ADOdb Data Dictionary
  1231.     * @access private
  1232.     */
  1233.     var $dict;
  1234.     
  1235.     /**
  1236.     * @var string Current XML element
  1237.     * @access private
  1238.     */
  1239.     var $currentElement = '';
  1240.     
  1241.     /**
  1242.     * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
  1243.     * @access private
  1244.     */
  1245.     var $upgrade = '';
  1246.     
  1247.     /**
  1248.     * @var string Optional object prefix
  1249.     * @access private
  1250.     */
  1251.     var $objectPrefix = '';
  1252.     
  1253.     /**
  1254.     * @var long    Original Magic Quotes Runtime value
  1255.     * @access private
  1256.     */
  1257.     var $mgq;
  1258.     
  1259.     /**
  1260.     * @var long    System debug
  1261.     * @access private
  1262.     */
  1263.     var $debug;
  1264.     
  1265.     /**
  1266.     * @var string Regular expression to find schema version
  1267.     * @access private
  1268.     */
  1269.     var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/';
  1270.     
  1271.     /**
  1272.     * @var string Current schema version
  1273.     * @access private
  1274.     */
  1275.     var $schemaVersion;
  1276.     
  1277.     /**
  1278.     * @var int    Success of last Schema execution
  1279.     */
  1280.     var $success;
  1281.     
  1282.     /**
  1283.     * @var bool    Execute SQL inline as it is generated
  1284.     */
  1285.     var $executeInline;
  1286.     
  1287.     /**
  1288.     * @var bool    Continue SQL execution if errors occur
  1289.     */
  1290.     var $continueOnError;
  1291.     
  1292.     /**
  1293.     * Creates an adoSchema object
  1294.     *
  1295.     * Creating an adoSchema object is the first step in processing an XML schema.
  1296.     * The only parameter is an ADOdb database connection object, which must already
  1297.     * have been created.
  1298.     *
  1299.     * @param object $db ADOdb database connection object.
  1300.     */
  1301.     function adoSchema( &$db ) {
  1302.         // Initialize the environment
  1303.         $this->mgq = get_magic_quotes_runtime();
  1304.         set_magic_quotes_runtime(0);
  1305.         
  1306.         $this->db =& $db;
  1307.         $this->debug = $this->db->debug;
  1308.         $this->dict = NewDataDictionary( $this->db );
  1309.         $this->sqlArray = array();
  1310.         $this->schemaVersion = XMLS_SCHEMA_VERSION;
  1311.         $this->executeInline( XMLS_EXECUTE_INLINE );
  1312.         $this->continueOnError( XMLS_CONTINUE_ON_ERROR );
  1313.         $this->setUpgradeMethod();
  1314.     }
  1315.     
  1316.     /**
  1317.     * Sets the method to be used for upgrading an existing database
  1318.     *
  1319.     * Use this method to specify how existing database objects should be upgraded.
  1320.     * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
  1321.     * alter each database object directly, REPLACE attempts to rebuild each object
  1322.     * from scratch, BEST attempts to determine the best upgrade method for each
  1323.     * object, and NONE disables upgrading.
  1324.     *
  1325.     * This method is not yet used by AXMLS, but exists for backward compatibility.
  1326.     * The ALTER method is automatically assumed when the adoSchema object is
  1327.     * instantiated; other upgrade methods are not currently supported.
  1328.     *
  1329.     * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
  1330.     * @returns string Upgrade method used
  1331.     */
  1332.     function SetUpgradeMethod( $method = '' ) {
  1333.         if( !is_string( $method ) ) {
  1334.             return FALSE;
  1335.         }
  1336.         
  1337.         $method = strtoupper( $method );
  1338.         
  1339.         // Handle the upgrade methods
  1340.         switch( $method ) {
  1341.             case 'ALTER':
  1342.                 $this->upgrade = $method;
  1343.                 break;
  1344.             case 'REPLACE':
  1345.                 $this->upgrade = $method;
  1346.                 break;
  1347.             case 'BEST':
  1348.                 $this->upgrade = 'ALTER';
  1349.                 break;
  1350.             case 'NONE':
  1351.                 $this->upgrade = 'NONE';
  1352.                 break;
  1353.             default:
  1354.                 // Use default if no legitimate method is passed.
  1355.                 $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD;
  1356.         }
  1357.         
  1358.         return $this->upgrade;
  1359.     }
  1360.     
  1361.     /**
  1362.     * Enables/disables inline SQL execution.
  1363.     *
  1364.     * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
  1365.     * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
  1366.     * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
  1367.     * to apply the schema to the database.
  1368.     *
  1369.     * @param bool $mode execute
  1370.     * @return bool current execution mode
  1371.     *
  1372.     * @see ParseSchema(), ExecuteSchema()
  1373.     */
  1374.     function ExecuteInline( $mode = NULL ) {
  1375.         if( is_bool( $mode ) ) {
  1376.             $this->executeInline = $mode;
  1377.         }
  1378.         
  1379.         return $this->executeInline;
  1380.     }
  1381.     
  1382.     /**
  1383.     * Enables/disables SQL continue on error.
  1384.     *
  1385.     * Call this method to enable or disable continuation of SQL execution if an error occurs.
  1386.     * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
  1387.     * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
  1388.     * of the schema will continue.
  1389.     *
  1390.     * @param bool $mode execute
  1391.     * @return bool current continueOnError mode
  1392.     *
  1393.     * @see addSQL(), ExecuteSchema()
  1394.     */
  1395.     function ContinueOnError( $mode = NULL ) {
  1396.         if( is_bool( $mode ) ) {
  1397.             $this->continueOnError = $mode;
  1398.         }
  1399.         
  1400.         return $this->continueOnError;
  1401.     }
  1402.     
  1403.     /**
  1404.     * Loads an XML schema from a file and converts it to SQL.
  1405.     *
  1406.     * Call this method to load the specified schema (see the DTD for the proper format) from
  1407.     * the filesystem and generate the SQL necessary to create the database described. 
  1408.     * @see ParseSchemaString()
  1409.     *
  1410.     * @param string $file Name of XML schema file.
  1411.     * @param bool $returnSchema Return schema rather than parsing.
  1412.     * @return array Array of SQL queries, ready to execute
  1413.     */
  1414.     function ParseSchema( $filename, $returnSchema = FALSE ) {
  1415.         return $this->ParseSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
  1416.     }
  1417.     
  1418.     /**
  1419.     * Loads an XML schema from a file and converts it to SQL.
  1420.     *
  1421.     * Call this method to load the specified schema from a file (see the DTD for the proper format) 
  1422.     * and generate the SQL necessary to create the database described by the schema.
  1423.     *
  1424.     * @param string $file Name of XML schema file.
  1425.     * @param bool $returnSchema Return schema rather than parsing.
  1426.     * @return array Array of SQL queries, ready to execute.
  1427.     *
  1428.     * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString()
  1429.     * @see ParseSchema(), ParseSchemaString()
  1430.     */
  1431.     function ParseSchemaFile( $filename, $returnSchema = FALSE ) {
  1432.         // Open the file
  1433.         if( !($fp = fopen( $filename, 'r' )) ) {
  1434.             // die( 'Unable to open file' );
  1435.             return FALSE;
  1436.         }
  1437.         
  1438.         // do version detection here
  1439.         if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) {
  1440.             return FALSE;
  1441.         }
  1442.         
  1443.         if ( $returnSchema )
  1444.         {
  1445.             $xmlstring = '';
  1446.             while( $data = fread( $fp, 100000 ) ) {
  1447.                 $xmlstring .= $data;
  1448.             }
  1449.             return $xmlstring;
  1450.         }
  1451.         
  1452.         $this->success = 2;
  1453.         
  1454.         $xmlParser = $this->create_parser();
  1455.         
  1456.         // Process the file
  1457.         while( $data = fread( $fp, 4096 ) ) {
  1458.             if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) {
  1459.                 die( sprintf(
  1460.                     "XML error: %s at line %d",
  1461.                     xml_error_string( xml_get_error_code( $xmlParser) ),
  1462.                     xml_get_current_line_number( $xmlParser)
  1463.                 ) );
  1464.             }
  1465.         }
  1466.         
  1467.         xml_parser_free( $xmlParser );
  1468.         
  1469.         return $this->sqlArray;
  1470.     }
  1471.     
  1472.     /**
  1473.     * Converts an XML schema string to SQL.
  1474.     *
  1475.     * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
  1476.     * and generate the SQL necessary to create the database described by the schema. 
  1477.     * @see ParseSchema()
  1478.     *
  1479.     * @param string $xmlstring XML schema string.
  1480.     * @param bool $returnSchema Return schema rather than parsing.
  1481.     * @return array Array of SQL queries, ready to execute.
  1482.     */
  1483.     function ParseSchemaString( $xmlstring, $returnSchema = FALSE ) {
  1484.         if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
  1485.             return FALSE;
  1486.         }
  1487.         
  1488.         // do version detection here
  1489.         if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) {
  1490.             return FALSE;
  1491.         }
  1492.         
  1493.         if ( $returnSchema )
  1494.         {
  1495.             return $xmlstring;
  1496.         }
  1497.         
  1498.         $this->success = 2;
  1499.         
  1500.         $xmlParser = $this->create_parser();
  1501.         
  1502.         if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) {
  1503.             die( sprintf(
  1504.                 "XML error: %s at line %d",
  1505.                 xml_error_string( xml_get_error_code( $xmlParser) ),
  1506.                 xml_get_current_line_number( $xmlParser)
  1507.             ) );
  1508.         }
  1509.         
  1510.         xml_parser_free( $xmlParser );
  1511.         
  1512.         return $this->sqlArray;
  1513.     }
  1514.     
  1515.     /**
  1516.     * Loads an XML schema from a file and converts it to uninstallation SQL.
  1517.     *
  1518.     * Call this method to load the specified schema (see the DTD for the proper format) from
  1519.     * the filesystem and generate the SQL necessary to remove the database described.
  1520.     * @see RemoveSchemaString()
  1521.     *
  1522.     * @param string $file Name of XML schema file.
  1523.     * @param bool $returnSchema Return schema rather than parsing.
  1524.     * @return array Array of SQL queries, ready to execute
  1525.     */
  1526.     function RemoveSchema( $filename, $returnSchema = FALSE ) {
  1527.         return $this->RemoveSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
  1528.     }
  1529.     
  1530.     /**
  1531.     * Converts an XML schema string to uninstallation SQL.
  1532.     *
  1533.     * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
  1534.     * and generate the SQL necessary to uninstall the database described by the schema. 
  1535.     * @see RemoveSchema()
  1536.     *
  1537.     * @param string $schema XML schema string.
  1538.     * @param bool $returnSchema Return schema rather than parsing.
  1539.     * @return array Array of SQL queries, ready to execute.
  1540.     */
  1541.     function RemoveSchemaString( $schema, $returnSchema = FALSE ) {
  1542.         
  1543.         // grab current version
  1544.         if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
  1545.             return FALSE;
  1546.         }
  1547.         
  1548.         return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema );
  1549.     }
  1550.     
  1551.     /**
  1552.     * Applies the current XML schema to the database (post execution).
  1553.     *
  1554.     * Call this method to apply the current schema (generally created by calling 
  1555.     * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes, 
  1556.     * and executing other SQL specified in the schema) after parsing.
  1557.     * @see ParseSchema(), ParseSchemaString(), ExecuteInline()
  1558.     *
  1559.     * @param array $sqlArray Array of SQL statements that will be applied rather than
  1560.     *        the current schema.
  1561.     * @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
  1562.     * @returns integer 0 if failure, 1 if errors, 2 if successful.
  1563.     */
  1564.     function ExecuteSchema( $sqlArray = NULL, $continueOnErr =  NULL ) {
  1565.         if( !is_bool( $continueOnErr ) ) {
  1566.             $continueOnErr = $this->ContinueOnError();
  1567.         }
  1568.         
  1569.         if( !isset( $sqlArray ) ) {
  1570.             $sqlArray = $this->sqlArray;
  1571.         }
  1572.         
  1573.         if( !is_array( $sqlArray ) ) {
  1574.             $this->success = 0;
  1575.         } else {
  1576.             $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr );
  1577.         }
  1578.         
  1579.         return $this->success;
  1580.     }
  1581.     
  1582.     /**
  1583.     * Returns the current SQL array. 
  1584.     *
  1585.     * Call this method to fetch the array of SQL queries resulting from 
  1586.     * ParseSchema() or ParseSchemaString(). 
  1587.     *
  1588.     * @param string $format Format: HTML, TEXT, or NONE (PHP array)
  1589.     * @return array Array of SQL statements or FALSE if an error occurs
  1590.     */
  1591.     function PrintSQL( $format = 'NONE' ) {
  1592.         $sqlArray = null;
  1593.         return $this->getSQL( $format, $sqlArray );
  1594.     }
  1595.     
  1596.     /**
  1597.     * Saves the current SQL array to the local filesystem as a list of SQL queries.
  1598.     *
  1599.     * Call this method to save the array of SQL queries (generally resulting from a
  1600.     * parsed XML schema) to the filesystem.
  1601.     *
  1602.     * @param string $filename Path and name where the file should be saved.
  1603.     * @return boolean TRUE if save is successful, else FALSE. 
  1604.     */
  1605.     function SaveSQL( $filename = './schema.sql' ) {
  1606.         
  1607.         if( !isset( $sqlArray ) ) {
  1608.             $sqlArray = $this->sqlArray;
  1609.         }
  1610.         if( !isset( $sqlArray ) ) {
  1611.             return FALSE;
  1612.         }
  1613.         
  1614.         $fp = fopen( $filename, "w" );
  1615.         
  1616.         foreach( $sqlArray as $key => $query ) {
  1617.             fwrite( $fp, $query . ";\n" );
  1618.         }
  1619.         fclose( $fp );
  1620.     }
  1621.     
  1622.     /**
  1623.     * Create an xml parser
  1624.     *
  1625.     * @return object PHP XML parser object
  1626.     *
  1627.     * @access private
  1628.     */
  1629.     function &create_parser() {
  1630.         // Create the parser
  1631.         $xmlParser = xml_parser_create();
  1632.         xml_set_object( $xmlParser, $this );
  1633.         
  1634.         // Initialize the XML callback functions
  1635.         xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' );
  1636.         xml_set_character_data_handler( $xmlParser, '_tag_cdata' );
  1637.         
  1638.         return $xmlParser;
  1639.     }
  1640.     
  1641.     /**
  1642.     * XML Callback to process start elements
  1643.     *
  1644.     * @access private
  1645.     */
  1646.     function _tag_open( &$parser, $tag, $attributes ) {
  1647.         switch( strtoupper( $tag ) ) {
  1648.             case 'TABLE':
  1649.                 $this->obj = new dbTable( $this, $attributes );
  1650.                 xml_set_object( $parser, $this->obj );
  1651.                 break;
  1652.             case 'SQL':
  1653.                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  1654.                     $this->obj = new dbQuerySet( $this, $attributes );
  1655.                     xml_set_object( $parser, $this->obj );
  1656.                 }
  1657.                 break;
  1658.             default:
  1659.                 // print_r( array( $tag, $attributes ) );
  1660.         }
  1661.         
  1662.     }
  1663.     
  1664.     /**
  1665.     * XML Callback to process CDATA elements
  1666.     *
  1667.     * @access private
  1668.     */
  1669.     function _tag_cdata( &$parser, $cdata ) {
  1670.     }
  1671.     
  1672.     /**
  1673.     * XML Callback to process end elements
  1674.     *
  1675.     * @access private
  1676.     * @internal
  1677.     */
  1678.     function _tag_close( &$parser, $tag ) {
  1679.         
  1680.     }
  1681.     
  1682.     /**
  1683.     * Converts an XML schema string to the specified DTD version.
  1684.     *
  1685.     * Call this method to convert a string containing an XML schema to a different AXMLS
  1686.     * DTD version. For instance, to convert a schema created for an pre-1.0 version for 
  1687.     * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 
  1688.     * parameter is specified, the schema will be converted to the current DTD version. 
  1689.     * If the newFile parameter is provided, the converted schema will be written to the specified
  1690.     * file.
  1691.     * @see ConvertSchemaFile()
  1692.     *
  1693.     * @param string $schema String containing XML schema that will be converted.
  1694.     * @param string $newVersion DTD version to convert to.
  1695.     * @param string $newFile File name of (converted) output file.
  1696.     * @return string Converted XML schema or FALSE if an error occurs.
  1697.     */
  1698.     function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) {
  1699.         
  1700.         // grab current version
  1701.         if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
  1702.             return FALSE;
  1703.         }
  1704.         
  1705.         if( !isset ($newVersion) ) {
  1706.             $newVersion = $this->schemaVersion;
  1707.         }
  1708.         
  1709.         if( $version == $newVersion ) {
  1710.             $result = $schema;
  1711.         } else {
  1712.             $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion);
  1713.         }
  1714.         
  1715.         if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
  1716.             fwrite( $fp, $result );
  1717.             fclose( $fp );
  1718.         }
  1719.         
  1720.         return $result;
  1721.     }
  1722.     
  1723.     // compat for pre-4.3 - jlim
  1724.     function _file_get_contents($path)
  1725.     {
  1726.         if (function_exists('file_get_contents')) return file_get_contents($path);
  1727.         return join('',file($path));
  1728.     }
  1729.     
  1730.     /**
  1731.     * Converts an XML schema file to the specified DTD version.
  1732.     *
  1733.     * Call this method to convert the specified XML schema file to a different AXMLS
  1734.     * DTD version. For instance, to convert a schema created for an pre-1.0 version for 
  1735.     * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 
  1736.     * parameter is specified, the schema will be converted to the current DTD version. 
  1737.     * If the newFile parameter is provided, the converted schema will be written to the specified
  1738.     * file.
  1739.     * @see ConvertSchemaString()
  1740.     *
  1741.     * @param string $filename Name of XML schema file that will be converted.
  1742.     * @param string $newVersion DTD version to convert to.
  1743.     * @param string $newFile File name of (converted) output file.
  1744.     * @return string Converted XML schema or FALSE if an error occurs.
  1745.     */
  1746.     function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) {
  1747.         
  1748.         // grab current version
  1749.         if( !( $version = $this->SchemaFileVersion( $filename ) ) ) {
  1750.             return FALSE;
  1751.         }
  1752.         
  1753.         if( !isset ($newVersion) ) {
  1754.             $newVersion = $this->schemaVersion;
  1755.         }
  1756.         
  1757.         if( $version == $newVersion ) {
  1758.             $result = _file_get_contents( $filename );
  1759.             
  1760.             // remove unicode BOM if present
  1761.             if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) {
  1762.                 $result = substr( $result, 3 );
  1763.             }
  1764.         } else {
  1765.             $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' );
  1766.         }
  1767.         
  1768.         if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
  1769.             fwrite( $fp, $result );
  1770.             fclose( $fp );
  1771.         }
  1772.         
  1773.         return $result;
  1774.     }
  1775.     
  1776.     function TransformSchema( $schema, $xsl, $schematype='string' )
  1777.     {
  1778.         // Fail if XSLT extension is not available
  1779.         if( ! function_exists( 'xslt_create' ) ) {
  1780.             return FALSE;
  1781.         }
  1782.         
  1783.         $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl';
  1784.         
  1785.         // look for xsl
  1786.         if( !is_readable( $xsl_file ) ) {
  1787.             return FALSE;
  1788.         }
  1789.         
  1790.         switch( $schematype )
  1791.         {
  1792.             case 'file':
  1793.                 if( !is_readable( $schema ) ) {
  1794.                     return FALSE;
  1795.                 }
  1796.                 
  1797.                 $schema = _file_get_contents( $schema );
  1798.                 break;
  1799.             case 'string':
  1800.             default:
  1801.                 if( !is_string( $schema ) ) {
  1802.                     return FALSE;
  1803.                 }
  1804.         }
  1805.         
  1806.         $arguments = array (
  1807.             '/_xml' => $schema,
  1808.             '/_xsl' => _file_get_contents( $xsl_file )
  1809.         );
  1810.         
  1811.         // create an XSLT processor
  1812.         $xh = xslt_create ();
  1813.         
  1814.         // set error handler
  1815.         xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
  1816.         
  1817.         // process the schema
  1818.         $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); 
  1819.         
  1820.         xslt_free ($xh);
  1821.         
  1822.         return $result;
  1823.     }
  1824.     
  1825.     /**
  1826.     * Processes XSLT transformation errors
  1827.     *
  1828.     * @param object $parser XML parser object
  1829.     * @param integer $errno Error number
  1830.     * @param integer $level Error level
  1831.     * @param array $fields Error information fields
  1832.     *
  1833.     * @access private
  1834.     */
  1835.     function xslt_error_handler( $parser, $errno, $level, $fields ) {
  1836.         if( is_array( $fields ) ) {
  1837.             $msg = array(
  1838.                 'Message Type' => ucfirst( $fields['msgtype'] ),
  1839.                 'Message Code' => $fields['code'],
  1840.                 'Message' => $fields['msg'],
  1841.                 'Error Number' => $errno,
  1842.                 'Level' => $level
  1843.             );
  1844.             
  1845.             switch( $fields['URI'] ) {
  1846.                 case 'arg:/_xml':
  1847.                     $msg['Input'] = 'XML';
  1848.                     break;
  1849.                 case 'arg:/_xsl':
  1850.                     $msg['Input'] = 'XSL';
  1851.                     break;
  1852.                 default:
  1853.                     $msg['Input'] = $fields['URI'];
  1854.             }
  1855.             
  1856.             $msg['Line'] = $fields['line'];
  1857.         } else {
  1858.             $msg = array(
  1859.                 'Message Type' => 'Error',
  1860.                 'Error Number' => $errno,
  1861.                 'Level' => $level,
  1862.                 'Fields' => var_export( $fields, TRUE )
  1863.             );
  1864.         }
  1865.         
  1866.         $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n"
  1867.                        . '<table>' . "\n";
  1868.         
  1869.         foreach( $msg as $label => $details ) {
  1870.             $error_details .= '<tr><td><b>' . $label . ': </b></td><td>' . htmlentities( $details ) . '</td></tr>' . "\n";
  1871.         }
  1872.         
  1873.         $error_details .= '</table>';
  1874.         
  1875.         trigger_error( $error_details, E_USER_ERROR );
  1876.     }
  1877.     
  1878.     /**
  1879.     * Returns the AXMLS Schema Version of the requested XML schema file.
  1880.     *
  1881.     * Call this method to obtain the AXMLS DTD version of the requested XML schema file.
  1882.     * @see SchemaStringVersion()
  1883.     *
  1884.     * @param string $filename AXMLS schema file
  1885.     * @return string Schema version number or FALSE on error
  1886.     */
  1887.     function SchemaFileVersion( $filename ) {
  1888.         // Open the file
  1889.         if( !($fp = fopen( $filename, 'r' )) ) {
  1890.             // die( 'Unable to open file' );
  1891.             return FALSE;
  1892.         }
  1893.         
  1894.         // Process the file
  1895.         while( $data = fread( $fp, 4096 ) ) {
  1896.             if( preg_match( $this->versionRegex, $data, $matches ) ) {
  1897.                 return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
  1898.             }
  1899.         }
  1900.         
  1901.         return FALSE;
  1902.     }
  1903.     
  1904.     /**
  1905.     * Returns the AXMLS Schema Version of the provided XML schema string.
  1906.     *
  1907.     * Call this method to obtain the AXMLS DTD version of the provided XML schema string.
  1908.     * @see SchemaFileVersion()
  1909.     *
  1910.     * @param string $xmlstring XML schema string
  1911.     * @return string Schema version number or FALSE on error
  1912.     */
  1913.     function SchemaStringVersion( $xmlstring ) {
  1914.         if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
  1915.             return FALSE;
  1916.         }
  1917.         
  1918.         if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) {
  1919.             return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
  1920.         }
  1921.         
  1922.         return FALSE;
  1923.     }
  1924.     
  1925.     /**
  1926.     * Extracts an XML schema from an existing database.
  1927.     *
  1928.     * Call this method to create an XML schema string from an existing database.
  1929.     * If the data parameter is set to TRUE, AXMLS will include the data from the database
  1930.     * in the schema. 
  1931.     *
  1932.     * @param boolean $data Include data in schema dump
  1933.     * @return string Generated XML schema
  1934.     */
  1935.     function ExtractSchema( $data = FALSE ) {
  1936.         $old_mode = $this->db->SetFetchMode( ADODB_FETCH_NUM );
  1937.         
  1938.         $schema = '<?xml version="1.0"?>' . "\n"
  1939.                 . '<schema version="' . $this->schemaVersion . '">' . "\n";
  1940.         
  1941.         if( is_array( $tables = $this->db->MetaTables( 'TABLES' ) ) ) {
  1942.             foreach( $tables as $table ) {
  1943.                 $schema .= '    <table name="' . $table . '">' . "\n";
  1944.                 
  1945.                 // grab details from database
  1946.                 $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE 1=1' );
  1947.                 $fields = $this->db->MetaColumns( $table );
  1948.                 $indexes = $this->db->MetaIndexes( $table );
  1949.                 
  1950.                 if( is_array( $fields ) ) {
  1951.                     foreach( $fields as $details ) {
  1952.                         $extra = '';
  1953.                         $content = array();
  1954.                         
  1955.                         if( $details->max_length > 0 ) {
  1956.                             $extra .= ' size="' . $details->max_length . '"';
  1957.                         }
  1958.                         
  1959.                         if( $details->primary_key ) {
  1960.                             $content[] = '<KEY/>';
  1961.                         } elseif( $details->not_null ) {
  1962.                             $content[] = '<NOTNULL/>';
  1963.                         }
  1964.                         
  1965.                         if( $details->has_default ) {
  1966.                             $content[] = '<DEFAULT value="' . $details->default_value . '"/>';
  1967.                         }
  1968.                         
  1969.                         if( $details->auto_increment ) {
  1970.                             $content[] = '<AUTOINCREMENT/>';
  1971.                         }
  1972.                         
  1973.                         // this stops the creation of 'R' columns,
  1974.                         // AUTOINCREMENT is used to create auto columns
  1975.                         $details->primary_key = 0;
  1976.                         $type = $rs->MetaType( $details );
  1977.                         
  1978.                         $schema .= '        <field name="' . $details->name . '" type="' . $type . '"' . $extra . '>';
  1979.                         
  1980.                         if( !empty( $content ) ) {
  1981.                             $schema .= "\n            " . implode( "\n            ", $content ) . "\n        ";
  1982.                         }
  1983.                         
  1984.                         $schema .= '</field>' . "\n";
  1985.                     }
  1986.                 }
  1987.                 
  1988.                 if( is_array( $indexes ) ) {
  1989.                     foreach( $indexes as $index => $details ) {
  1990.                         $schema .= '        <index name="' . $index . '">' . "\n";
  1991.                         
  1992.                         if( $details['unique'] ) {
  1993.                             $schema .= '            <UNIQUE/>' . "\n";
  1994.                         }
  1995.                         
  1996.                         foreach( $details['columns'] as $column ) {
  1997.                             $schema .= '            <col>' . $column . '</col>' . "\n";
  1998.                         }
  1999.                         
  2000.                         $schema .= '        </index>' . "\n";
  2001.                     }
  2002.                 }
  2003.                 
  2004.                 if( $data ) {
  2005.                     $rs = $this->db->Execute( 'SELECT * FROM ' . $table );
  2006.                     
  2007.                     if( is_object( $rs ) ) {
  2008.                         $schema .= '        <data>' . "\n";
  2009.                         
  2010.                         while( $row = $rs->FetchRow() ) {
  2011.                             foreach( $row as $key => $val ) {
  2012.                                 $row[$key] = htmlentities($val);
  2013.                             }
  2014.                             
  2015.                             $schema .= '            <row><f>' . implode( '</f><f>', $row ) . '</f></row>' . "\n";
  2016.                         }
  2017.                         
  2018.                         $schema .= '        </data>' . "\n";
  2019.                     }
  2020.                 }
  2021.                 
  2022.                 $schema .= '    </table>' . "\n";
  2023.             }
  2024.         }
  2025.         
  2026.         $this->db->SetFetchMode( $old_mode );
  2027.         
  2028.         $schema .= '</schema>';
  2029.         return $schema;
  2030.     }
  2031.     
  2032.     /**
  2033.     * Sets a prefix for database objects
  2034.     *
  2035.     * Call this method to set a standard prefix that will be prepended to all database tables 
  2036.     * and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix.
  2037.     *
  2038.     * @param string $prefix Prefix that will be prepended.
  2039.     * @param boolean $underscore If TRUE, automatically append an underscore character to the prefix.
  2040.     * @return boolean TRUE if successful, else FALSE
  2041.     */
  2042.     function SetPrefix( $prefix = '', $underscore = TRUE ) {
  2043.         switch( TRUE ) {
  2044.             // clear prefix
  2045.             case empty( $prefix ):
  2046.                 logMsg( 'Cleared prefix' );
  2047.                 $this->objectPrefix = '';
  2048.                 return TRUE;
  2049.             // prefix too long
  2050.             case strlen( $prefix ) > XMLS_PREFIX_MAXLEN:
  2051.             // prefix contains invalid characters
  2052.             case !preg_match( '/^[a-z][a-z0-9_]+$/i', $prefix ):
  2053.                 logMsg( 'Invalid prefix: ' . $prefix );
  2054.                 return FALSE;
  2055.         }
  2056.         
  2057.         if( $underscore AND substr( $prefix, -1 ) != '_' ) {
  2058.             $prefix .= '_';
  2059.         }
  2060.         
  2061.         // prefix valid
  2062.         logMsg( 'Set prefix: ' . $prefix );
  2063.         $this->objectPrefix = $prefix;
  2064.         return TRUE;
  2065.     }
  2066.     
  2067.     /**
  2068.     * Returns an object name with the current prefix prepended.
  2069.     *
  2070.     * @param string    $name Name
  2071.     * @return string    Prefixed name
  2072.     *
  2073.     * @access private
  2074.     */
  2075.     function prefix( $name = '' ) {
  2076.         // if prefix is set
  2077.         if( !empty( $this->objectPrefix ) ) {
  2078.             // Prepend the object prefix to the table name
  2079.             // prepend after quote if used
  2080.             return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name );
  2081.         }
  2082.         
  2083.         // No prefix set. Use name provided.
  2084.         return $name;
  2085.     }
  2086.     
  2087.     /**
  2088.     * Checks if element references a specific platform
  2089.     *
  2090.     * @param string $platform Requested platform
  2091.     * @returns boolean TRUE if platform check succeeds
  2092.     *
  2093.     * @access private
  2094.     */
  2095.     function supportedPlatform( $platform = NULL ) {
  2096.         $regex = '/^(\w*\|)*' . $this->db->databaseType . '(\|\w*)*$/';
  2097.         
  2098.         if( !isset( $platform ) OR preg_match( $regex, $platform ) ) {
  2099.             logMsg( "Platform $platform is supported" );
  2100.             return TRUE;
  2101.         } else {
  2102.             logMsg( "Platform $platform is NOT supported" );
  2103.             return FALSE;
  2104.         }
  2105.     }
  2106.     
  2107.     /**
  2108.     * Clears the array of generated SQL.
  2109.     *
  2110.     * @access private
  2111.     */
  2112.     function clearSQL() {
  2113.         $this->sqlArray = array();
  2114.     }
  2115.     
  2116.     /**
  2117.     * Adds SQL into the SQL array.
  2118.     *
  2119.     * @param mixed $sql SQL to Add
  2120.     * @return boolean TRUE if successful, else FALSE.
  2121.     *
  2122.     * @access private
  2123.     */    
  2124.     function addSQL( $sql = NULL ) {
  2125.         if( is_array( $sql ) ) {
  2126.             foreach( $sql as $line ) {
  2127.                 $this->addSQL( $line );
  2128.             }
  2129.             
  2130.             return TRUE;
  2131.         }
  2132.         
  2133.         if( is_string( $sql ) ) {
  2134.             $this->sqlArray[] = $sql;
  2135.             
  2136.             // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL.
  2137.             if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) {
  2138.                 $saved = $this->db->debug;
  2139.                 $this->db->debug = $this->debug;
  2140.                 $ok = $this->db->Execute( $sql );
  2141.                 $this->db->debug = $saved;
  2142.                 
  2143.                 if( !$ok ) {
  2144.                     if( $this->debug ) {
  2145.                         ADOConnection::outp( $this->db->ErrorMsg() );
  2146.                     }
  2147.                     
  2148.                     $this->success = 1;
  2149.                 }
  2150.             }
  2151.             
  2152.             return TRUE;
  2153.         }
  2154.         
  2155.         return FALSE;
  2156.     }
  2157.     
  2158.     /**
  2159.     * Gets the SQL array in the specified format.
  2160.     *
  2161.     * @param string $format Format
  2162.     * @return mixed SQL
  2163.     *    
  2164.     * @access private
  2165.     */
  2166.     function getSQL( $format = NULL, $sqlArray = NULL ) {
  2167.         if( !is_array( $sqlArray ) ) {
  2168.             $sqlArray = $this->sqlArray;
  2169.         }
  2170.         
  2171.         if( !is_array( $sqlArray ) ) {
  2172.             return FALSE;
  2173.         }
  2174.         
  2175.         switch( strtolower( $format ) ) {
  2176.             case 'string':
  2177.             case 'text':
  2178.                 return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : '';
  2179.             case'html':
  2180.                 return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : '';
  2181.         }
  2182.         
  2183.         return $this->sqlArray;
  2184.     }
  2185.     
  2186.     /**
  2187.     * Destroys an adoSchema object.
  2188.     *
  2189.     * Call this method to clean up after an adoSchema object that is no longer in use.
  2190.     * @deprecated adoSchema now cleans up automatically.
  2191.     */
  2192.     function Destroy() {
  2193.         set_magic_quotes_runtime( $this->mgq );
  2194.         unset( $this );
  2195.     }
  2196. }
  2197.  
  2198. /**
  2199. * Message logging function
  2200. *
  2201. * @access private
  2202. */
  2203. function logMsg( $msg, $title = NULL, $force = FALSE ) {
  2204.     if( XMLS_DEBUG or $force ) {
  2205.         echo '<pre>';
  2206.         
  2207.         if( isset( $title ) ) {
  2208.             echo '<h3>' . htmlentities( $title ) . '</h3>';
  2209.         }
  2210.         
  2211.         if( is_object( $this ) ) {
  2212.             echo '[' . get_class( $this ) . '] ';
  2213.         }
  2214.         
  2215.         print_r( $msg );
  2216.         
  2217.         echo '</pre>';
  2218.     }
  2219. }
  2220. ?>